﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace DotNetExtensions.AspNet.Helpers
{
    public class TypeInvoker
    {
        public string Module { get; set; }
        public string Clazz { get; set; }

        public TypeInvoker()
        {

        }

        public object Invoke(string method)
        {
            object invoker = TypeHelper.CreateInstance(Module, Clazz);
            if (invoker == null) throw new Exception($"`{Clazz}` not find at {Module}.");
            var methodInfo = invoker.GetType().GetMethod(method);
            if (methodInfo == null) throw new Exception($"Method `{method}` not find at `{Clazz}`");
            return methodInfo.Invoke(invoker, null);
        }
        public object Invoke(string method, params object[] parameters)
        {
            object invoker = TypeHelper.CreateInstance(Module, Clazz);
            if (invoker == null) throw new Exception($"`{Clazz}` not find at {Module}.");
            var methodInfo = invoker.GetType().GetMethod(method);
            if (methodInfo == null) throw new Exception($"Method `{method}` not find at `{Clazz}`");
            return methodInfo.Invoke(invoker, parameters);
        }
    }

    public class JsonObjectInvoker : TypeInvoker
    {
        public string Data { get; set; }

        public JsonObjectInvoker()
        {

        }

        public JsonObjectInvoker(TypeInvoker invoker)
        {
            this.Module = invoker.Module;
            this.Clazz = invoker.Clazz;
        }

        public object New()
        {
            Type type = TypeHelper.TryParse(Module, Clazz);
            return JsonConvert.DeserializeObject(Data, type);
        }
        public string ToJson()
        {
            return JsonConvert.SerializeObject(this);
        }

        public static JsonObjectInvoker Parse(object data)
        {
            if (data == null) return null;
            if (TypeHelper.IsBasicType(data.GetType())) return null;

            JsonObjectInvoker invoker = new JsonObjectInvoker(TypeHelper.TryParse(data.GetType()));
            invoker.Data = JsonConvert.SerializeObject(data);
            return invoker;
        }

        public static object From(string data)
        {
            if (data == null) return null;
            var invoker = JsonConvert.DeserializeObject<JsonObjectInvoker>(data);
            return invoker.New();
        }
    }
    public class TypeHelper
    {
        public static Assembly[] FindAssemblys(string module)
        {
            return AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetModules().Any(m => m.Name == module)).ToArray();
        }
        public static Type TryParse(string module, string clazz)
        {
            if (string.IsNullOrEmpty(module) || string.IsNullOrEmpty(clazz)) throw new ArgumentNullException();

            var assemblys = FindAssemblys(module);

            if (assemblys == null || assemblys.Length == 0) throw new ArgumentException($"Module `{module}` not find ");

            Type type = assemblys.First().GetType(clazz);

            if (type == null) throw new ArgumentException($"Type `{clazz}` not find ");

            return type;
        }
        public static object CreateInstance(string module, string clazz)
        {
            if (string.IsNullOrEmpty(module) || string.IsNullOrEmpty(clazz)) throw new ArgumentNullException();
            var assemblys = FindAssemblys(module);
            if (assemblys == null || assemblys.Length == 0) throw new ArgumentException($"Module `{module}` not find ");
            var assembly = assemblys.First();
            return assembly.CreateInstance(clazz);
        }
        public static TypeInvoker TryParse(Type type)
        {
            TypeInvoker invoker = new TypeInvoker();
            invoker.Clazz = type.FullName;
            invoker.Module = type.Module.Name;
            return invoker;
        }
        public static bool IsBasicType(Type type)
        {
            if (type == typeof(string)) return true;
            if (type == typeof(String)) return true;
            if (type == typeof(int)) return true;
            if (type == typeof(Int16)) return true;
            if (type == typeof(Int32)) return true;
            if (type == typeof(Int64)) return true;
            if (type == typeof(long)) return true;
            if (type == typeof(Boolean)) return true;
            if (type == typeof(bool)) return true;
            if (type == typeof(float)) return true;
            if (type == typeof(double)) return true;
            if (type == typeof(decimal)) return true;
            if (type == typeof(Decimal)) return true;
            if (type == typeof(uint)) return true;
            if (type == typeof(ulong)) return true;
            if (type == typeof(ushort)) return true;
            if (type == typeof(short)) return true;
            if (type == typeof(UInt16)) return true;
            if (type == typeof(UInt32)) return true;
            if (type == typeof(UInt64)) return true;
            if (type == typeof(DateTime)) return true;
            if (type == typeof(DateTime?)) return true;

            return false;
        }

        public static object DefaultValue(Type type)
        {
            if (type == typeof(string)) return string.Empty;
            if (type == typeof(String)) return string.Empty;
            if (type == typeof(int)) return 0;
            if (type == typeof(Int16)) return 0;
            if (type == typeof(Int32)) return 0;
            if (type == typeof(Int64)) return 0;
            if (type == typeof(long)) return 0;
            if (type == typeof(Boolean)) return false;
            if (type == typeof(bool)) return false;
            if (type == typeof(float)) return 0;
            if (type == typeof(double)) return 0;
            if (type == typeof(decimal)) return 0;
            if (type == typeof(Decimal)) return 0;
            if (type == typeof(uint)) return 0;
            if (type == typeof(ulong)) return 0;
            if (type == typeof(ushort)) return 0;
            if (type == typeof(short)) return 0;
            if (type == typeof(UInt16)) return 0;
            if (type == typeof(UInt32)) return 0;
            if (type == typeof(UInt64)) return 0;
            if (type == typeof(DateTime)) return DateTime.Now;
            if (type == typeof(DateTime?)) return DateTime.Now;

            return null;
        }

        public static JToken GetStruct(Type type)
        {
            if (type == null) return null;
            JToken structs;
            if (IsArray(type))
            {
                JToken sub = GetStruct(type.GetGenericArguments().FirstOrDefault());
                structs = new JArray(sub);
            }
            else
            {
                structs = GetObjectStruct(type);
            }
            return structs;
        }
        private static JToken GetObjectStruct(Type type)
        {
            JObject structs = new JObject();
            foreach (var item in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (IsBasicType(item.PropertyType))
                {
                    structs.Add(item.Name, JToken.FromObject(DefaultValue(item.PropertyType)));
                }
                else if (IsArray(item.PropertyType))
                {
                    Type gt = item.PropertyType.GetGenericArguments().FirstOrDefault();
                    JToken subs = GetObjectStruct(gt);
                    structs.Add(item.Name, new JArray(subs));
                }
                else if (item.PropertyType.IsClass)
                {
                    var subs = GetStruct(item.PropertyType);
                    structs.Add(item.Name, subs);
                }
            }

            return structs;
        }

        public static Dictionary<string, object> GetStructDict(Type type)
        {
            if (type == null) return null;
            Dictionary<string, object> structs = new Dictionary<string, object>();
            foreach (var item in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (IsBasicType(item.PropertyType))
                {
                    structs.Add(item.Name, DefaultValue(item.PropertyType));
                }
                else if (IsArray(item.PropertyType))
                {
                    Type gt = item.PropertyType.GetGenericArguments().FirstOrDefault();
                    object subs = GetStructDict(gt);
                    structs.Add(item.Name, new List<object> { subs });
                }
                else if (item.PropertyType.IsClass)
                {
                    var subs = GetStruct(item.PropertyType);
                    structs.Add(item.Name, subs);
                }
            }

            return structs;
        }

        public static bool IsArray(Type type)
        {
            if (type == null || IsBasicType(type)) return false;
            var interfaces = type.GetInterfaces();

            if (type.IsGenericType == false) return false;
            if (type.IsArray) return true;

            foreach (var item in interfaces)
            {
                if (item.Name.Contains("IList")) return true;
                if (item.Name.Contains("IEnumerable")) return true;
                if (item.Name.Contains("ICollection")) return true;
            }

            return false;
        }

        public static string GetStructJson(Type type)
        {
            if (type == null) return string.Empty;
            JToken jtoken = GetStruct(type);
            return JsonConvert.SerializeObject(jtoken, Formatting.Indented);
        }
    }
}
